package fr.grassaud.hmvc.session;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.grassaud.hmvc.event.HMVCEvent;
import fr.grassaud.hmvc.event.HMVCEvent.EventType;
import fr.grassaud.hmvc.util.HMVCIntrospector;

public class HMVCSessionImpl implements HMVCSessionManager {
    

    private Triad root;
    private Triad current;

    private List<TriadListener> triadListeners;

    private Logger logger = LoggerFactory.getLogger(HMVCSessionImpl.class);

    
    private Map<String,Object> attributes;

    /**
     * Creates the session, but without root class
     */
    public HMVCSessionImpl() {
	super();
	attributes = new HashMap<String, Object>();
	addTriadListener(new SessionTriadListener(this));
    }

    
    /**
     * Adds the given triad listener to this session
     * @param listener the listener to add
     */
    public void addTriadListener(TriadListener listener) {
	if (this.triadListeners == null) {
	    this.triadListeners = new ArrayList<TriadListener>();
	}
	this.triadListeners.add(listener);
    }

    
    /**
     * Removes the given triad listener from this session
     * @param listener the listener to remove
     */
    public void removeTriadListener(TriadListener listener) {
	if (this.triadListeners != null) {
	    this.triadListeners.remove(listener);
	}
    }
    
    /**
     * Adds a new attributes to this session
     * @param name the name of the attribute to add
     * @param object the value of the attribute to add
     */
    public void addAttribute(String name, Object object) {
	this.attributes.put(name, object);
    }
    
    
    /**
     * Returns the attribute from this session with the given name
     * @param name the name of the attribute to get
     * @return the object corresponding to the given attribute name, or <code>null</code> if none
     */
    @SuppressWarnings("unchecked")
    public <T> T getAttribute(String name) {
	return (T) this.attributes.get(name);
    }
    
    /**
     * Removes the attribute with the given name from this session
     * @param name the name of the attribute to remove
     */
    public void removeAttribute(String name) {
	this.attributes.remove(name);
    }

    /**
     * Returns all attributes names of this session
     * @return all attributes names of this session
     */
    public Set<String> getAttributeNames() {
	return this.attributes.keySet();
    }
    
    
  
    
   

    
    /**
     * Sends a <code>ViewEvent</code>. Assures that the sender is well a view component
     * @param sender the sender of the event
     * @param event the event to send
     */
    public boolean  sendViewEvent(Object sender, HMVCEvent event) {
	event.setEventType(EventType.VIEW);
	if (!HMVCIntrospector.isViewComponent(sender)) {
	    throw new IllegalStateException("the sender should be annotated by the View annotation");
	}
	return sendGenericEvent(sender, event);
    }

    
    /**
     * Sends a <code>ModelEvent</code>. Assures that the sender is well a model component
     * @param sender the sender of the event
     * @param event the event to send
     */
    public boolean sendModelEvent(Object sender, HMVCEvent event) {
	event.setEventType(EventType.MODEL);
	if (!HMVCIntrospector.isModelComponent(sender)) {
	    throw new IllegalStateException("the sender should be annotated by the Model annotation");
	}
	return sendGenericEvent(sender, event);
    }

    
    /**
     * Sends a <code>ControllerEvent</code>. assures that the sender is well a controller component
     * @param sender the sender of the event
     * @param event the event to send
     */
    public boolean sendControllerEvent(Object sender, HMVCEvent event) {
	event.setEventType(EventType.CONTROLLER);
	if (!HMVCIntrospector.isControllerComponent(sender)) {
	    throw new IllegalStateException("the sender should be annotated by the Controller annotation");
	}
	return sendGenericEvent(sender, event);
    }

    
    /**
     * Sends a generic event in the session
     * @param sender the component that sends the event
     * @param event the event to send
     */
    private boolean sendGenericEvent(Object sender, HMVCEvent event) {
	boolean result = false;
	Triad triad = getTriadOfComponent(event.getSource());
	if (triad == null) {
	    throw new IllegalStateException("cannot find a triad with the sender " + sender);
	}
	current = triad;
	if (current.canConsumeEvent(event)) {
	    result = current.consumeEvent(event);
	} else {
	    // now check if a child can consume the event
	    triad = HMVCIntrospector.getChildWithEvent(event, current);
	    // if there are no children that can consume the event, we have to
	    // check if the parent can
	    if (triad == null) {
		triad = HMVCIntrospector.getParentWithEvent(event, current);
	    }
	    if (triad != null) {
		current = triad;
		result = current.consumeEvent(event);
	    } else {
		logger.warn("the event of type {} with name {} was ignored", event.getClass(), event.getName());
	    }
	}
	return result;
    }

    /**
     * Fires to the <code>TriadListener</code> of this session that a triad is created but not instanciated yet
     * @param triad the tiad created
     */
    public void fireTriadCreated(Triad triad) {
	if (this.triadListeners != null) {
	    for (TriadListener listener : triadListeners) {
		listener.onTriadCreated(triad);
	    }
	}
    }

    /**
     * Fires to the <code>TriadListener</code> of this session that a triad is instantiated
     * @param triad the triad instantiated
     */
    public void fireTriadInstantiated(Triad triad) {
	if (this.triadListeners != null) {
	    for (TriadListener listener : triadListeners) {
		listener.onTriadInstanciated(triad);
	    }
	}
    }

    
    /**
     * Sets the root controller class of this session
     * @param rootController the class of the controller, should be annotated as a <code>Controller</code> component
     */
    public final void setRoot(Class<?> rootController) {
	// now create the root triad
	root = new TriadImpl(rootController,this);
	current = root;
	root.instantiate();

    }
    
    
    public Class<?> getRootControllerClass() {
	return this.root.getTriadInfo().getControllerClass();
    }

    @SuppressWarnings("unchecked")
    /**
     * Returns the main view of this session
     * @return the main view of this session
     */
    public <T> T getMainView() {
	return (T) root.getView();
    }
    
    
    
    /**
     * Returns the triad corresponding to the given component
     * @param component a component, should be a <code>View, Model or Controller</code>
     * @return the triad found or <code>null</code> if none
     */
    private Triad getTriadOfComponent(Object component) {
	if ( current == null) {
	    current = root;
	}
	Triad result = null;
	if (current.hasComponent(component)) {
	    result = current;
	} else {
	    // search the controller class that managed the given component,
	    // note it can be the controller itself
	    Class<?> controllerClass = HMVCIntrospector.getControllerClass(component);
	    if (controllerClass == null) {
		throw new IllegalStateException(
			"The controller of the given component was not found.A component should be managed by a controller.");
	    }
	    List<Triad> triads = root.getTriadWithController(controllerClass);
	    for (Triad t : triads) {
		if (t.hasComponent(component)) {
		    result = t;
		    break;
		}
	    }
	}
	return result;
    }
    
    
/**
 * Returns the <code>Triad</code> root of this session
 * @return the <code>Triad</code> root of this session
 */
    public Triad getRoot() {
	return root;
    }
    
      
    
    
    public String getId() {
	return this.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(this));
    }
    

}
